// 目标：模拟缓冲区可以同时容纳多个商品，生产者可连续放入，消费者在缓冲区非空时取走商品。
// 关键点：
//        std::mutex 保护共享缓冲区 std::deque<int>。
//        std::condition_variable 负责在缓冲区“空 / 满”状态变化时通知对方线程。
//        cond.wait(lock, predicate) 保证线程被唤醒后重新检查状态，避免虚假唤醒。
//        notify_all() 唤醒所有等待线程，让唤醒后的线程再次竞争互斥锁。

#include <condition_variable>
#include <deque>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

// 互斥锁：保证对共享容器的互斥访问
std::mutex mtx;

// 条件变量：生产者与消费者依赖的同步原语
std::condition_variable cond;

// 共享缓冲区：可容纳多个商品，采用 deque 便于首尾操作
std::deque<int> buffer;

// 缓冲区最大容量（模拟先生产后消费的批量情况）
constexpr std::size_t kMaxBufferSize = 5;

// 每个生产者计划生产的商品数量
constexpr int kItemsPerProducer = 8;

/**
 * 生产者线程函数
 * @param id 生产者编号，用于输出日志区分不同线程
 */
void producer(int id) {
    for (int i = 0; i < kItemsPerProducer; ++i) {
        std::unique_lock<std::mutex> lock(mtx);  // 加锁：保护 buffer 状态

        // 当缓冲区已满时，生产者进入等待状态
        // wait 会在内部循环中执行：
        //   1. 原子地释放锁
        //   2. 将线程加入 cond 等待队列
        //   3. 被唤醒后重新加锁并再次判断谓词
        cond.wait(lock, [] { return buffer.size() < kMaxBufferSize; });

        // 缓冲区有空位，生成一个商品（使用 id 和序号编码）
        const int item = id * 100 + i;
        buffer.push_back(item);

        std::cout << "[Producer " << id << "] produced " << item
                  << ", buffer size = " << buffer.size() << '\n';

        // 通知所有等待线程：缓冲区状态发生变化
        cond.notify_all();
        // lock 作用域结束后自动解锁
    }
}

/**
 * 消费者线程函数
 * @param id 消费者编号
 * @param totalToConsume 计划消费的商品数量
 */
void consumer(int id, int totalToConsume) {
    for (int i = 0; i < totalToConsume; ++i) {
        std::unique_lock<std::mutex> lock(mtx);  // 加锁访问 buffer

        // 当缓冲区为空时，消费者等待
        cond.wait(lock, [] { return !buffer.empty(); });

        // 现在缓冲区非空，取出一个商品
        int item = buffer.front();
        buffer.pop_front();

        std::cout << "    [Consumer " << id << "] consumed " << item
                  << ", buffer size = " << buffer.size() << '\n';

        // 通知：缓冲区可能不再满了，唤醒潜在的生产者线程
        cond.notify_all();
    }
}

int main() {
    const int producerCount = 3;  // 生产者数量
    const int consumerCount = 2;  // 消费者数量

    // 总商品数量 = 生产者数量 × 每个生产者的产量
    const int totalItems = producerCount * kItemsPerProducer;

    // 为简化，平均分配给消费者
    const int itemsPerConsumer = totalItems / consumerCount;

    std::vector<std::thread> producers;
    std::vector<std::thread> consumers;

    // 启动多个生产者
    for (int i = 0; i < producerCount; ++i) {
        producers.emplace_back(producer, i);
    }

    // 启动多个消费者
    for (int i = 0; i < consumerCount; ++i) {
        consumers.emplace_back(consumer, i, itemsPerConsumer);
    }

    // 等待所有线程完成
    for (auto& t : producers) t.join();
    for (auto& t : consumers) t.join();

    std::cout << "All tasks completed.\n";
    return 0;
}